package com.github.kezhenxu94.crawler.source.rss;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Stack;
import java.util.logging.Logger;

/**
 * Created by kezhenxu on 12/4/16.
 *
 * @author kezhenxu (kezhenxu94@163.com)
 */
public class Rss2Handler extends DefaultHandler implements Rss2ElementNames {
    private static final Logger logger = Logger.getLogger(Rss2Handler.class.getSimpleName());

    protected Rss2Channel channel;
    protected Rss2Image image;
    protected Rss2Item item;

    protected DateFormat dateFormat;
    protected Stack<String> tags;

    private StringBuilder content;
    private boolean inContent;

    public Rss2Handler() {
        dateFormat = DateFormat.getDateInstance();
        tags = new Stack<>();
        content = new StringBuilder();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        inContent = false;
        switch (qName) {
            case CHANNEL:
                channel = new Rss2Channel();
                break;
            case Channel.IMAGE:
                image = new Rss2Image();
                String url = attributes.getValue("url");
                String title = attributes.getValue("title");
                String link = attributes.getValue("link");
                image.setUrl(url);
                image.setTitle(title);
                image.setLink(link);
                break;
            case Channel.ITEM:
                item = new Rss2Item();
                break;
        }
        tags.push(qName);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        inContent = false;
        String currentTag = tags.pop();
        if ("rss".equalsIgnoreCase(currentTag)) { // DONE
            return;
        }
        String parentTag = tags.peek();
        if ("channel".equalsIgnoreCase(parentTag)) {
            doParseChannelDirectChildren(qName);
        } else if ("item".equalsIgnoreCase(parentTag)) {
            doParseItemDirectChildren(qName);
        } else if ("image".equalsIgnoreCase(parentTag)) {
            doParseImageDirectChildren(qName);
        }
    }

    private void doParseImageDirectChildren(String qName) {
        switch (qName) {
            case Channel.Image.URL:
                image.setUrl(content.toString());
                break;
            case Channel.Image.TITLE:
                image.setTitle(content.toString());
                break;
            case Channel.Image.LINK:
                image.setLink(content.toString());
                break;
            case Channel.Image.WIDTH:
                image.setWidth(Double.parseDouble(content.toString()));
                break;
            case Channel.Image.HEIGHT:
                image.setHeight(Double.parseDouble(content.toString()));
                break;
            case Channel.Image.DESCRIPTION:
                image.setDescription(content.toString());
                break;
        }
    }

    private void doParseItemDirectChildren(String qName) {
        switch (qName) {
            case Channel.Item.TITLE:
                item.setTitle(content.toString());
                break;
            case Channel.Item.LINK:
                item.setLink(content.toString());
                break;
            case Channel.Item.DESCRIPTION:
                item.setDescription(content.toString());
                break;
            case Channel.Item.AUTHOR:
                item.setAuthor(content.toString());
                break;
            case Channel.Item.CATEGORY:
                item.setCategory(content.toString());
                break;
            case Channel.Item.COMMENTS:
                item.setComments(content.toString());
                break;
            case Channel.Item.ENCLOSURE:
                item.setEnclosure(content.toString());
                break;
            case Channel.Item.GUID:
                item.setGuid(content.toString());
                break;
            case Channel.Item.PUB_DATE:
                try {
                    item.setPubDate(getDateFormat().parse(content.toString()));
                } catch (ParseException e) {
                    logger.warning("Error occurs while parsing pubDate in item: " + content.toString());
                }
                break;
            case Channel.Item.SOURCE:
                item.setSource(content.toString());
                break;
        }
    }

    private void doParseChannelDirectChildren(String qName) {
        switch (qName) {
            case Channel.TITLE:
                channel.setTitle(content.toString());
                break;
            case Channel.LINK:
                channel.setLink(content.toString());
                break;
            case Channel.DESCRIPTION:
                channel.setDescription(content.toString());
                break;
            case Channel.LANGUAGE:
                channel.setLanguage(content.toString());
                break;
            case Channel.COPYRIGHT:
                channel.setCopyright(content.toString());
                break;
            case Channel.MANAGING_EDITOR:
                channel.setManagingEditor(content.toString());
                break;
            case Channel.WEB_MASTER:
                channel.setWebMaster(content.toString());
                break;
            case Channel.PUB_DATE:
                try {
                    channel.setPubDate(getDateFormat().parse(content.toString()));
                } catch (ParseException e) {
                    logger.warning("Error occurs while parsing pubDate in channel: " + content.toString());
                }
                break;
            case Channel.LAST_BUILD_DATE:
                try {
                    channel.setLastBuildDate(getDateFormat().parse(content.toString()));
                } catch (ParseException e) {
                    logger.warning("Error occurs while parsing pubDate in channel: " + content.toString());
                }
                break;
            case Channel.CATEGORY:
                channel.setCategory(content.toString());
                break;
            case Channel.GENERATOR:
                channel.setGenerator(content.toString());
                break;
            case Channel.DOCS:
                channel.setDocs(content.toString());
                break;
            case Channel.CLOUD:
                channel.setCloud(content.toString());
                break;
            case Channel.TTL:
                channel.setTtl(Long.parseLong(content.toString()));
                break;
            case Channel.IMAGE:
                channel.setImage(image);
                break;
            case Channel.RATING:
                channel.setRating(content.toString());
                break;
            case Channel.TEXT_INPUT:
                channel.setTextInput(content.toString());
                break;
            case Channel.SKIP_HOURS:
                channel.setSkipHours(content.toString());
                break;
            case Channel.SKIP_DAYS:
                channel.setSkipDays(content.toString());
                break;
            case Channel.ITEM:
                channel.getItems().add(item);
                break;
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (!inContent) {
            content.delete(0, content.length());
        }
        content.append(ch, start, length);
        inContent = true;
    }

    public Rss2Channel getChannel() {
        return channel;
    }

    public void setDateFormat(DateFormat dateFormat) {
        this.dateFormat = dateFormat;
    }

    public DateFormat getDateFormat() {
        return dateFormat;
    }
}
